package omega

import (
	"context"
	"reflect"
	"sync"
)

var (
	config             *Config
	cancel             func()
	transportInstance  *transport
	compensateInstance *compensate
	coordinateInstance *coordinate
	initOnce           sync.Once
)

func init() {
	compensateInstance = &compensate{
		funcs: make(map[string]reflect.Value),
	}
	coordinateInstance = &coordinate{
		funcs: make(map[string]reflect.Value),
	}
}

func DecorateSagaMethod(sagaStartPtr, target interface{}, timeout int, autoClose bool) error {
	sagaStartInjectBefore := func(ctx context.Context, _ ...interface{}) (context.Context, error) {
		return SagaStartBeforeInject(ctx, timeout, getFnName(target))
	}

	sagaStartInjectAfter := func(ctx context.Context, err error) error {
		return SagaEndAfterInject(ctx, getFnName(target), err, autoClose)
	}

	err := decorate(sagaStartPtr, target, sagaStartInjectBefore, sagaStartInjectAfter)
	if err != nil {
		return err
	}
	return nil
}

func DecorateSagaEndMethod(sagaStartPtr, target interface{}) error {
	sagaEndInjectBefore := func(ctx context.Context, _ ...interface{}) (context.Context, error) {
		return nil, nil
	}
	sagaEndInjectAfter := func(ctx context.Context, err error) error {
		return SendSagaEndEvent(ctx, getFnName(target), err)
	}
	err := decorate(sagaStartPtr, target, sagaEndInjectBefore, sagaEndInjectAfter)
	if err != nil {
		return err
	}
	return nil
}

func DecorateTxMethod(txPtr, target, compensate interface{}, timeout int) error {
	targetFuncName := getFnName(target)
	compensateFuncName := getFnName(compensate)

	compensateInstance.registerFunc(compensateFuncName, compensate)

	compensateInjectBefore := func(ctx context.Context, in []reflect.Value) (context.Context, error) {
		return TxStartBeforeInject(ctx, targetFuncName, compensateFuncName, in, timeout)
	}

	compensateInjectAfter := func(ctx context.Context, err error) error {
		return TxEndAfterInject(ctx, targetFuncName, compensateFuncName, err)
	}

	err := decorate(txPtr, target, compensateInjectBefore, compensateInjectAfter)
	if err != nil {
		return err
	}
	return nil
}

func DecorateTccMethod(tccStartPtr, target interface{}, autoClose bool) error {
	tccStartInjectBefore := func(ctx context.Context, _ ...interface{}) (context.Context, error) {
		return TccStartBeforeInject(ctx)
	}

	tccStartInjectAfter := func(ctx context.Context, err error) error {
		return TccEndAfterInject(ctx, getFnName(target), err, autoClose)
	}

	err := decorate(tccStartPtr, target, tccStartInjectBefore, tccStartInjectAfter)
	if err != nil {
		return err
	}
	return nil
}

func DecorateTccEndMethod(tccStartPtr, target interface{}) error {
	tccStartInjectBefore := func(ctx context.Context, _ ...interface{}) (context.Context, error) {
		return nil, nil
	}

	tccStartInjectAfter := func(ctx context.Context, err error) error {
		return SendTccEndEvent(ctx, getFnName(target), err)
	}

	err := decorate(tccStartPtr, target, tccStartInjectBefore, tccStartInjectAfter)
	if err != nil {
		return err
	}
	return nil
}

func DecoratePartitionMethod(partitionPtr, target, confirm, cancel interface{}) error {
	//targetFuncName := getFnName(target)
	confirmFuncName := getFnName(confirm)
	cancelFuncName := getFnName(cancel)

	coordinateInstance.registerFunc(confirmFuncName, confirm)
	coordinateInstance.registerFunc(cancelFuncName, cancel)

	partitionInjectBefore := func(ctx context.Context, in []reflect.Value) (context.Context, error) {
		return TccTryStartBefore(ctx, confirmFuncName, cancelFuncName)
	}

	partitionInjectAfter := func(ctx context.Context, err error) error {
		return TccTryEndAfterInject(ctx, confirmFuncName, cancelFuncName, err)
	}

	err := decorate(partitionPtr, target, partitionInjectBefore, partitionInjectAfter)
	if err != nil {
		return err
	}
	return nil
}

func InitOmega(ctx context.Context, cfg *Config) (err error) {
	initOnce.Do(func() {
		ctx, cancel = context.WithCancel(ctx)
		if cfg.encode == nil {
			cfg.encode = &gobEncode{}
		}

		config = cfg
		transportInstance, err = newTransport(cfg.Transport)
		if err != nil {
			return
		}
		if err = transportInstance.handleTx(ctx); err != nil {
			return
		}
		if err = transportInstance.handleTcc(ctx); err != nil {
			return
		}
		compensateInstance.schedule(ctx, transportInstance.getCompensateCmdChan())
		coordinateInstance.schedule(ctx, transportInstance.getTccCoordinateCmdChan())
	})
	return
}

func CloseOmega() {
	cancel()
	_ = transportInstance.txOnDisconnected()
	_ = transportInstance.tccOnDisconnected()
}
